package com.itcast.service;

import com.itcast.mapper.RedisMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class RedisService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisMapper redisMapper;

    /**
     * 缓存击穿是指缓存中不存在但数据库中存在的数据被大量请求，导致请求直接落到了数据库上，导致数据库的压力增大，系统性能下降的问题。
     * 解决缓存击穿的方法有以下几种：
     *
     * （1）设置热点数据永远不过期：对于一些重要的热点数据，可以将其设置为永远不过期，避免了过期时间到了而缓存失效的问题。
     *
     * （2）加互斥锁(mutex key)：当缓存失效的时候，使用互斥锁(mutex key)机制，让只有一个线程去请求数据库，其他线程等待结果。避免了大量请求落到数据库上的问题，减轻了数据库的压力。
     *
     * （3）提前异步加载缓存：使用缓存预热机制，在系统启动时，提前异步地加载热点数据到缓存中，避免了缓存失效时的瞬间流量冲击。
     */

    /**
     * 使用互斥锁解决缓存击穿
     * @param shopId
     * @return
     */
    public Integer queryStock(Integer shopId){
        //查询缓存
        String key = "cache:"+shopId;
        Integer stockCache = (Integer) redisTemplate.opsForValue().get(key);

        //如果存在直接返回
        if(stockCache!=null){
            return stockCache;
        }

        //添加互斥锁访问数据库
        String lockKey = "lock:"+shopId;
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "0", 10, TimeUnit.SECONDS);

        //若没有抢到锁，则失败重试
        if(Boolean.FALSE.equals(lock)){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return queryStock(shopId);
        }

        //若抢到锁，则直接查询数据库，并缓存在缓存中
        Integer stock = redisMapper.queryStock(shopId);
        redisTemplate.opsForValue().set(key,stock,10,TimeUnit.SECONDS);
        log.info("有人来访问数据库了...");

        return stock;
    }


    /**
     * 秒杀功能
     * 乐观锁解决超卖问题
     * @param shopId
     */
    public void secKill(Integer shopId){
        redisMapper.updateStock(shopId);
    }

    /**
     * 秒杀功能
     * 分布式锁解决一人一单问题
     * lua脚本解决误删问题
     * @param shopId
     * @param userId
     */
    public String secKill(Integer shopId,Integer userId){
        //查询库存是否充足
        Integer stock = redisMapper.queryStock(shopId);

        if(stock<1){
            return "库存不足";
        }

        //获取锁
        String key = "disLock:"+userId;
        String value = "threadId:"+Thread.currentThread().getId();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(key ,value ,30, TimeUnit.SECONDS);

        if(Boolean.FALSE.equals(lock)){
            return "不允许重复下单";
        }

        //扣减库存
        try {
            redisMapper.updateStock(shopId);
        } finally {
            //释放锁

            //用lua脚本
            String script = "if (redis.call('GET', KEYS[1]) == ARGV[1]) then return redis.call('DEL', KEYS[1]) end return 0";
            redisTemplate.execute(new DefaultRedisScript(script, Long.class),Collections.singletonList(key),Collections.singletonList(value));

            //不用lua脚本
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//            String threadId = (String) redisTemplate.opsForValue().get(key);
//            if(threadId.equals(value)){
//                redisTemplate.delete(key);
//            }
        }
        return "下单成功";
    }
}
